package com.by.toolkit.utils;

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicate;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 重试工具类
 *
 * @author YanBingHao
 * @since 2024/1/26
 */
@Slf4j
public class RetryUtil {

    /**
     * 默认重试：每100ms重试一次
     *
     * @param supplier
     * @param retryNum
     * @param <T>
     * @return
     */
    public static <T> T retry(Supplier<T> supplier, int retryNum) {
        return retryByMills(supplier, retryNum, 100);
    }

    /**
     * 递增等待
     *
     * @param supplier
     * @param retryNum
     * @param backoff
     * @param unit
     * @param <T>
     * @return
     */
    public static <T> T retryByIncrWait(Supplier<T> supplier, int retryNum, long backoff, TimeUnit unit) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.incrementingWait(1, unit, backoff, unit))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryIfException failed after maximum retries", e);
        }
    }

    /**
     * 指定异常重试
     * 默认重试：每100ms重试一次
     *
     * @param supplier
     * @param retryNum
     * @param <T>
     * @return
     */
    public static <T> T retryIfException(Supplier<T> supplier, Class<? extends Throwable> retryException, int retryNum) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfExceptionOfType(retryException)
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryIfException failed after maximum retries", e);
        }
    }

    /**
     * 指定异常重试
     * 默认重试：每100ms重试一次
     *
     * @param supplier
     * @param retryNum
     * @param <T>
     * @return
     */
    public static <T> T retryIfException(Supplier<T> supplier,int retryNum,Predicate<Throwable> ePredicate) {

        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException(ePredicate)
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryIfException failed after maximum retries", e);
        }
    }

    /**
     * 抛Exception异常后重试
     * 重试间隔：毫秒
     *
     * @param supplier
     * @param retryNum
     * @param backoffMillis
     * @param <T>
     * @return
     */
    public static <T> T retryByMills(Supplier<T> supplier, int retryNum, long backoffMillis) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffMillis, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryByMills failed after maximum retries", e);
        }
    }

    /**
     * 抛Exception异常后重试
     * 重试间隔：秒
     *
     * @param supplier
     * @param retryNum
     * @param backoffSec
     * @param <T>
     * @return
     */
    public static <T> T retryBySeconds(Supplier<T> supplier, int retryNum, long backoffSec) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffSec, TimeUnit.SECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryBySeconds failed after maximum retries", e);
        }
    }

    /**
     * 抛异常重试
     * 指定重试间隔
     *
     * @param supplier
     * @param retryNum
     * @param timeUnit
     * @param backoffSec
     * @param <T>
     * @return
     */
    public static <T> T retry(Supplier<T> supplier, int retryNum, TimeUnit timeUnit, long backoffSec) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffSec, timeUnit))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retry failed after maximum retries", e);
        }
    }

    /**
     * 当返回结果为空重试
     * 默认重试间隔：毫秒
     *
     * @param supplier
     * @param retryNum
     * @param backoffSec
     * @param <T>
     * @return
     */
    public static <T> T retryIfResultNull(Supplier<T> supplier, int retryNum, long backoffSec) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .retryIfResult(Objects::isNull)
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffSec, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryIfResultNull failed after maximum retries", e);
        }
    }

    /**
     * 支持传入重试条件
     * 默认重试间隔：毫秒
     *
     * @param supplier
     * @param retryCondition
     * @param retryNum
     * @param backoffSec
     * @param <T>
     * @return
     */
    public static <T> T retryIfResult(Supplier<T> supplier, @Nonnull Predicate<T> retryCondition, int retryNum, long backoffSec) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .retryIfResult(retryCondition::test)
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffSec, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(supplier::get);
        } catch (Exception e) {
            throw new RuntimeException("retryIfResult failed after maximum retries", e);
        }
    }

    /**
     * 默认重试：每100ms重试一次
     *
     * @param callable
     * @param retryNum
     * @param <T>
     * @return
     */
    public static <T> T retryCallable(Callable<T> callable, int retryNum) {
        return retryByMillsretryCallable(callable, retryNum, 100);
    }

    /**
     * 抛Exception异常后重试
     * 重试间隔：毫秒
     *
     * @param retryNum
     * @param backoffMillis
     * @param <T>
     * @return
     */
    public static <T> T retryByMillsretryCallable(Callable<T> callable, int retryNum, long backoffMillis) {
        int maxAttempts = retryNum > 0 ? retryNum + 1 : 0;
        Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
                .retryIfException()
                .withStopStrategy(StopStrategies.stopAfterAttempt(maxAttempts))
                .withWaitStrategy(WaitStrategies.fixedWait(backoffMillis, TimeUnit.MILLISECONDS))
                .build();

        try {
            return retryer.call(callable);
        } catch (Exception e) {
            throw new RuntimeException("retryByMills failed after maximum retries", e);
        }
    }

}
